/*
 * Copyright (C) 2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Types
 */
typedef struct _ssa_recurse_t {
    evalue_t	*esp;
    empz_t	 mpz;
} ssa_recurse_t;

/*
 * Prototypes
 */
static inline eint32_t
ssa_count_get(esymbol_t *symbol);

static inline void
ssa_count_inc(esymbol_t *symbol);

static inline eint32_t
ssa_count_inc_get(esymbol_t *symbol);

static inline void
ssa_invalidate(esymbol_t **changes, eint32_t num_changes);

static void
ssa_add_target(elabel_t *label);

static void
ssa_set_target_vector(elabel_t *label);

static void
ssa_associate(efunction_t *function);

static void
ssa_function_state(efunction_t *function, esymbol_t *symbol, eint32_t state);

static void
ssa_source(efunction_t *function);

static void
ssa_loops_increment(elabel_t *label, eint32_t ident);

static void
ssa_loops_extend(elabel_t *label, eint32_t ident, eint32_t loop);

static void
ssa_loops(elabel_t *label);

static void
ssa_srefs(elabel_t *label);

static void
ssa_recurse(elabel_t *label, elabel_t *source, ssa_recurse_t *recurse);

static void
ssa_phi_recurse(elabel_t *label, eint32_t loop);

static void
ssa_linear(elabel_t *label, elabel_t **labels);

static void
ssa_labels(efunction_t *function);

static void
ssa_name(efunction_t *function);

static essa_load_t *
ssa_load_allocate(efunction_t *function, eint32_t bit, eint32_t ssa);

static void
ssa_clear_loads(efunction_t *function);

static void
ssa_clear_stores(efunction_t *function);

/*
 * Initialization
 */
static evector_t	*ssa_root;
static evector_t	*ssa_vector;
static evector_t	*ssa_changes;
static evector_t	*ssa_escapes;
static evector_t	*ssa_targets;

/*
 * Implementation
 */
static void
efold_init(evector_t *vector, eint32_t count)
{
    if (count + 1 > vector->length)
	erenew_vector(vector, ((count + 16) & ~15));
    memset(vector->v.obj, 0, (count + 1) * sizeof(eobject_t));
    vector->offset = count;
}

static void
efold_safe_push(evector_t *vector, eobject_t value)
{
    eobject_t	*pointer;

    if (vector->offset >= vector->length)
	erenew_vector(vector, (vector->length + 16));
    if (vector->offset >= 0) {
	pointer = vector->v.ptr;
	pointer[vector->offset] = value;
    }
    ++vector->offset;
}

static void
efold_safe_dec(evector_t *vector, eint32_t count)
{
    vector->offset -= count;
}

static eobject_t
efold_safe_top(evector_t *vector)
{
    eobject_t	*pointer;

    pointer = vector->v.ptr;

    return (vector->offset >= 0 ? pointer[vector->offset] : null);
}

static eobject_t
efold_safe_get(evector_t *vector, eint32_t offset)
{
    eobject_t	*pointer;

    pointer = vector->v.ptr;
    /* argument is negative */
    offset = vector->offset + offset;
    assert(offset < vector->length);

    return (offset >= 0 ? pointer[offset] : null);
}

static void
efold_safe_put(evector_t *vector, eint32_t offset, eobject_t value)
{
    eobject_t	*pointer;

    pointer = vector->v.ptr;
    /* argument is negative */
    offset = vector->offset + offset;
    assert(offset < vector->length);
    if (offset >= 0)
	pointer[offset] = value;
}

void
init_ssa(void)
{
    eobject_t	*root;

    evector(&ssa_root, t_void, 4, 1);
    eadd_root((eobject_t *)&ssa_root);
    root = ssa_root->v.ptr;

    evector(&ssa_vector, t_void, 16, 1);
    root[0] = ssa_vector;

    evector(&ssa_changes, t_void, 16, 1);
    root[1] = ssa_changes;

    evector(&ssa_escapes, t_void, 16, 1);
    root[2] = ssa_escapes;

    evector(&ssa_targets, t_void, 16, 1);
    root[3] = ssa_targets;

}

void
finish_ssa(void)
{
    erem_root((eobject_t *)&ssa_root);
}

void
essa(efunction_t *function)
{
    east_node_t		 *node;
    east_list_t		 *list;
    east_list_t		 *prev;
    elabel_t		 *temp;
    eint32_t		  ident;
    elabel_t		 *label;
    elabel_t		**labels;

    eclr_hash(function->hsym);
    function->vsym->offset = 0;
    mpz_set_ui(function->esc, 0);
    mpz_set_ui(function->ptr, 0);
    mpz_set_ui(function->set, 0);
    eclr_hash(function->functions);

    /* get initial basic block information */
    list = function->code;
    node = east_node(list);
    label = node->clabel.label;
    mpz_set_ui(label->set, 0);
    mpz_set_ui(label->use, 0);
    mpz_set_ui(label->phi, 0);
    memset(label->srcs->v.obj, 0, label->srcs->offset * sizeof(eobject_t));
    label->srcs->offset = 0;
    memset(label->dsts->v.obj, 0, label->dsts->offset * sizeof(eobject_t));
    label->dsts->offset = 0;

    /* first basic block doesn't have extra operands in stack */
    label->stack = label->loop = 0;
    label->sref = label->block = 0;

    prev = list, list = east_next(list);
    for (; list; prev = list, list = east_next(list)) {
	node = east_node(list);
	switch (node->code) {

	    case eop_label:
	    node_label:
		label->next = node->clabel.label;
		label = label->next;
		/* initialize to invalid/unknown value */
		label->stack = -1;
		label->loop = 0;
		label->sref = label->block = 0;
		mpz_set_ui(label->set, 0);
		mpz_set_ui(label->use, 0);
		mpz_set_ui(label->phi, 0);
		memset(label->srcs->v.obj, 0,
		       label->srcs->offset * sizeof(eobject_t));
		label->srcs->offset = 0;
		memset(label->dsts->v.obj, 0,
		       label->dsts->offset * sizeof(eobject_t));
		label->dsts->offset = 0;
		break;

	    case eop_block:
		/* not following branch */
		eremove_block(function, node->clabel.label);
		east_next(prev) = east_next(list);
		list = prev;
		break;

	    case eop_jt:			case eop_jf:
	    node_branch:
		if (east_next(list) == null)
		    break;
		prev = list;
		list = east_next(list);
		node = east_node(list);
		switch (node->code) {
		    case eop_label:		case eop_block:
			goto node_label;
		    case eop_jt:		case eop_jf:
			goto node_branch;
		    default:
			break;
		}

		/* create basic block after conditional branch */
		label->next = ecreate_label(function);
		label = label->next;
		label->stack = -1;
		label->block = 1;
		list = east_label_no_link(ecode_block(label));
		east_next(list) = east_next(prev);
		east_next(prev) = list;
		break;

	    default:
		break;
	}
    }

    label->next = null;

    labels = function->labels->v.obj;
    label = east_node(function->code)->clabel.label;
    for (ident = 0; label; ident++, label = label->next) {
	if (label->ident != ident) {
	    temp = labels[label->ident];
	    temp->ident = label->ident;
	    labels[label->ident] = temp;
	    label->ident = ident;
	    labels[ident] = label;
	}
    }

    ssa_associate(function);

    ssa_source(function);

    ssa_labels(function);

    ssa_name(function);
}

static inline eint32_t
ssa_count_get(esymbol_t *symbol)
{
    if (symbol->ssa & 0x80000000)
	symbol->ssa &= ~0x80000000;

    return (symbol->ssa);
}

static inline void
ssa_count_inc(esymbol_t *symbol)
{
    if (!(symbol->ssa & 0x80000000)) {
	++symbol->ssa;
	assert(symbol->ssa < most_positive_fix32);
	symbol->ssa |= 0x80000000;
    }
}

/*
 * ensure a new identifier is used
 */
static inline eint32_t
ssa_count_inc_get(esymbol_t *symbol)
{
#if 0
    /* FIXME this should be enough, but to work correctly, would need to
     * better logic to cause an "indirect load/store" when a called
     * function may have changed the value, otherwise, most times the
     * alternate code just causes a double increment... */
    if (symbol->ssa & 0x80000000)
	symbol->ssa &= ~0x80000000;
    else {
	++symbol->ssa;
	assert(symbol->ssa < most_positive_fix32);
    }
#else
    if (symbol->ssa & 0x80000000)
	symbol->ssa &= ~0x80000000;
    ++symbol->ssa;
    assert(symbol->ssa < most_positive_fix32);
#endif

    return (symbol->ssa);
}

static inline void
ssa_invalidate(esymbol_t **changes, eint32_t num_changes)
{
    while (num_changes--)
	ssa_count_inc(*changes++);
}

static void
ssa_add_target(elabel_t *label)
{
    eint32_t		  bot;
    eint32_t		  top;
    elabel_t		**labels;
    eint32_t		  offset;

    bot = 0;
    top = ssa_targets->offset;
    labels = ssa_targets->v.obj;
    for (offset = (bot + top) >> 1; bot < top; offset = (bot + top) >> 1) {
	if (labels[offset] == label)
	    return;
	if (label->ident > labels[offset]->ident)
	    bot = offset + 1;
	else
	    top = offset;
    }

    if (ssa_targets->offset >= ssa_targets->length) {
	erenew_vector(ssa_targets, ssa_targets->length + 16);
	labels = ssa_targets->v.obj;
    }
    if (offset < ssa_targets->offset)
	memmove(labels + offset + 1, labels + offset,
		(ssa_targets->offset - offset) * sizeof(eobject_t));
    labels[offset] = label;
    ++ssa_targets->offset;
}

static void
ssa_set_target_vector(elabel_t *label)
{
    eint32_t		  offset;
    evector_t		 *vector;

    if (ssa_targets->offset) {
	vector = label->dsts;
	offset = ssa_targets->offset;
	if (offset >= vector->length)
	    erenew_vector(vector, (offset + 4) & ~3);
	vector->offset = offset;
	offset *= sizeof(eobject_t);
	memcpy(vector->v.obj, ssa_targets->v.obj, offset);
	memset(ssa_targets->v.obj, 0, offset);
	ssa_targets->offset = 0;
    }
}

/*
 * create information about basic block dsts and initial ssa information
 * about symbols being referenced, modified, or address taken
 */
static void
ssa_associate(efunction_t *function)
{
    eint32_t		  i;
    east_node_t		 *node;
    eentry_t		 *iter;
    ebool_t		  jump;
    ehash_t		 *hash;
    east_list_t		 *list;
    esymbol_t		 *temp;
    eentry_t		 *entry;
    elabel_t		 *label;
    esymbol_t		 *value;
    efunction_t		 *lambda;

    /* vector to store symbolic stack representation */
    efold_init(ssa_vector, 0);

    function->flags = 0;

    node = east_node(function->code);
    label = node->clabel.label;

    /* no predecessor */
    label->ijmp = true;
    jump = false;
    value = null;
    for (list = east_next(function->code); list; list = east_next(list)) {
	node = east_node(list);
	/* remember if basic block can end in an unconditional jump */
	switch (node->code) {
	    case eop_j:
	    case eop_ret:			case eop_rett:
	    case eop_reti:			case eop_retit:
	    case eop_throw:
	    case eop_exit:
		jump = true;
		break;
	    case eop_label:			case eop_block:
	    case eop_note:
		break;
	    default:
		jump = false;
		break;
	}
	switch (node->code) {
	    case eop_label:			case eop_block:
		/* FIXME actually, only switches can have multiple
		 * targets, that are hashed by switch test value,
		 * not target, so would require ssa_targets to filter
		 * out duplicates
		 * FIXME "normal" blocks have at most two targets,
		 * one being conditional, and the other being the
		 * next in the flow
		 * FIXME if basic blocks are split once finding an
		 * eop_jh (switch dispatch) could use ssa_targets
		 * only there, reducing computations a bit...
		 */
		label->ojmp = !!jump;
		/* if basic block can start executing next one */
		if (!jump && label->next)
		    ssa_add_target(label->next);
		ssa_set_target_vector(label);
		label = node->clabel.label;
		label->ijmp = !!jump;
		/* don't yet have information about stack at start,
		 * this is the reason of the efold_safe_xxx() functions,
		 * that will return null if the non scalar field access
		 * happens to have arguments computed from a branch, what
		 * causes it to not optimize/fold the access */
		efold_init(ssa_vector, 0);
		value = null;
		break;
	    case eop_j:				case eop_jt:
	    case eop_jf:
		ssa_add_target(node->clabel.label);
		break;
	    case eop_jh:
		hash = els[immediate_offset(node)];
		for (i = 0; i < hash->size; i++) {
		    for (iter = hash->entries[i]; iter; iter = iter->next) {
			node = iter->value;
			ssa_add_target(node->clabel.label);
		    }
		}
		break;
		/* prepare to call function */
	    case eop_begin:
		for (i = node->cstack.length; i; i--)
		    efold_safe_push(ssa_vector, null);
		break;
		/* push value */
	    case eop_off:			case eop_push:
	    case eop_pusht:			case eop_pushv:
		efold_safe_push(ssa_vector, value);
		break;
		/* adjust stack */
	    case eop_unwind:
		if (node->cexcept.arg)
		    efold_safe_dec(ssa_vector, 4);
		value = null;
		break;
	    case eop_vnew:
		efold_safe_dec(ssa_vector, node->cvector.rank - 1);
	    case eop_new:
		function->flags |= fun_chg;
		value = null;
		break;
		/* non scalar operations that pop stack,
		 * invalidate value and create an escaped reference */
	    case eop_lh:
		function->flags |= fun_esc;
		efold_safe_dec(ssa_vector, 1);
		if ((value = efold_safe_top(ssa_vector))) {
		    essa_symbol_state(function, label, value, ssa_esc);
		    value = null;
		}
		break;
	    case eop_lv:			case eop_pv:
		function->flags |= fun_esc;
		efold_safe_dec(ssa_vector, node->cvector.rank);
		if ((value = efold_safe_top(ssa_vector))) {
		    essa_symbol_state(function, label, value, ssa_esc);
		    value = null;
		}
		break;
		/* binary expressions */
	    case eop_sref:
		label->sref = true;
		function->flags |= fun_chg;
	    case eop_ne:			case eop_lt:
	    case eop_le:			case eop_eq:
	    case eop_ge:			case eop_gt:
	    case eop_and:			case eop_or:
	    case eop_xor:			case eop_mul2:
	    case eop_div2:			case eop_shl:
	    case eop_shr:			case eop_add:
	    case eop_sub:			case eop_mul:
	    case eop_div:			case eop_trunc2:
	    case eop_rem:			case eop_atan2:
	    case eop_pow:			case eop_hypot:
	    case eop_complex:			case eop_szofdim:
	    case eop_subtpof:			case eop_sva:
		efold_safe_dec(ssa_vector, 1);
		value = null;
		break;
		/* pop value from stack */
	    case eop_pop:
		efold_safe_dec(ssa_vector, 1);
		value = efold_safe_top(ssa_vector);
		break;
		/* non scalar operations that pop stack */
	    case eop_svi:			case eop_sr:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, 1);
		if ((temp = efold_safe_top(ssa_vector)))
		    essa_symbol_state(function, label, temp, ssa_esc);
		break;
		/* non scalar operations that pop value from stack */
	    case eop_mv:			case eop_vmv:
	    case eop_renew:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, 1);
		if ((value = efold_safe_top(ssa_vector)))
		    essa_symbol_state(function, label, value, ssa_set);
		break;
		/* non scalar operations that pop stack twice */
	    case eop_sh:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, 2);
		if ((temp = efold_safe_top(ssa_vector)))
		    essa_symbol_state(function, label, temp, ssa_esc);
		break;
	    case eop_sv:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, node->cvector.rank + 1);
		if ((temp = efold_safe_top(ssa_vector)))
		    essa_symbol_state(function, label, temp, ssa_esc);
		break;
		/* load value dereferencing sp */
	    case eop_ls:
		value = efold_safe_get(ssa_vector, node->cstack.offset);
		break;
		/* store value dereferencing sp */
	    case eop_ss:			case eop_sst:
	    case eop_ssv:
		efold_safe_put(ssa_vector, node->cstack.offset, value);
		break;
		/* value is referenced */
	    case eop_ld:			case eop_ldt:
	    case eop_lb:			case eop_lbt:
	    case eop_lc:
		value = node->csymbol.symbol;
		essa_symbol_state(function, label, value, ssa_use);
		break;
		/* value is modified */
	    case eop_sd:			case eop_sdt:
	    case eop_sc:
		function->flags |= fun_chg;
	    case eop_sb:			case eop_sbt:
		value = node->csymbol.symbol;
		essa_symbol_state(function, label, value, ssa_set);
		break;
		/* simple function calls */
	    case eop_call:			case eop_blt:
		efold_safe_dec(ssa_vector, node->capply.length);
		ssa_function_state(function, node->capply.symbol, ssa_use);
		value = null;
		break;
	    case eop_meth:
		/* virtual method call not be resolved here */
		function->flags |= fun_jmp;
	    case eop_ecm:
		efold_safe_dec(ssa_vector, node->capply.length);
		ssa_function_state(function, node->capply.symbol, ssa_use);
		value = null;
		break;
	    case eop_apply:
		efold_safe_dec(ssa_vector, node->capply.length);
		value = null;
		break;
	    case eop_val:
		efold_safe_dec(ssa_vector, node->cstack.length);
		break;
	    case eop_fref:
		function->flags |= fun_esc;
		/* don't know if called, but remember it should not be
		 * ommited, unless it can be proven to not be called */
		ssa_function_state(function, node->capply.symbol, ssa_ptr);
		value = null;
		break;
		/* non scalar operations without stack effect that
		 * invalidate value and create an escaped reference */
	    case eop_pvi:			case eop_pr:
	    case eop_lvi:			case eop_lr:
		function->flags |= fun_esc;
		if (value) {
		    essa_symbol_state(function, label, value, ssa_esc);
		    value = null;
		}
		break;
		/* remember value address is taken and reset value */
	    case eop_pd:			case eop_pdt:
	    case eop_pb:			case eop_pbt:
		function->flags |= fun_esc;
		essa_symbol_state(function, label,
				 node->csymbol.symbol, ssa_ptr);
		value = null;
		break;
	    case eop_lhref:
		function->flags |= fun_esc;
		if ((temp = efold_safe_top(ssa_vector)))
		    essa_symbol_state(function, label, temp, ssa_esc);
		if (node->chash.arg)
		    efold_safe_push(ssa_vector, null);
		value = null;
		break;
	    case eop_lvref:
		function->flags |= fun_esc;
		if ((temp = efold_safe_get(ssa_vector, -node->cvector.rank)))
		    essa_symbol_state(function, label, temp, ssa_esc);
		efold_safe_dec(ssa_vector, node->cvector.rank -
			       !!node->cvector.arg - 1);
		value = null;
		break;
	    case eop_lrref:
		function->flags |= fun_esc;
		efold_safe_push(ssa_vector, null);
		if (node->crecord.arg)
		    efold_safe_push(ssa_vector, null);
		if (value)
		    essa_symbol_state(function, label, value, ssa_esc);
		break;
	    case eop_shref:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, !!node->chash.arg + 1);
		value = null;
		break;
	    case eop_svref:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, !!node->cvector.arg + 1);
		value = null;
		break;
	    case eop_srref:
		function->flags |= fun_chg;
		efold_safe_dec(ssa_vector, !!node->crecord.arg + 1);
		value = null;
		break;
		/* keep value */
	    case eop_aret:			case eop_vas:
	    case eop_svai:			case eop_note:
		break;
	    case eop_try:
		for (i = 4; i; i--)
		    efold_safe_push(ssa_vector, null);
	    case eop_throw:
		function->flags |= fun_jmp;
	    default:
		value = null;
		break;
	}
    }

    /* last basic block may end in an unconditional jump  */
    ssa_set_target_vector(label);

    /* root function is not considered constant, and is not callable */
    if (cfg_pass && function->name) {
	for (i = 0; i < function->functions->size; i++) {
	    entry = function->functions->entries[i];
	    for (; entry; entry = entry->next) {
		value = entry->name;
		lambda = value->value;
		function->flags |= lambda->flags;
	    }
	}
    }
}

void
essa_symbol_state(efunction_t *function, elabel_t *label,
		  esymbol_t *symbol, eint32_t state)
{
    eentry_t	*entry;
    eint32_t	 offset;

    if ((entry = eget_hash(function->hsym, symbol)) == null) {
	esymbol_t	**pointer;

	if (function->vsym->offset >= function->vsym->length)
	    erenew_vector(function->vsym, function->vsym->length + 8);
	pointer = function->vsym->v.obj;
	pointer[function->vsym->offset] = symbol;
	offset = function->vsym->offset++;
	enew(&entry, entry);
	entry->name = symbol;
	entry->value = efixnum(offset);
	eput_hash(function->hsym, entry);
    }
    else
	offset = eget_fixnum(entry->value);

    switch (state) {
	case ssa_use:
	    if (!mpz_tstbit(label->set, offset))
		mpz_setbit(label->use, offset);
	    break;
	case ssa_set:
	    mpz_setbit(label->set, offset);
	    mpz_setbit(function->set, offset);
	    break;
	case ssa_esc:
	    mpz_setbit(function->esc, offset);
	    break;
	case ssa_ptr:
	    mpz_setbit(function->ptr, offset);
	    break;
	default:
	    abort();
    }
}

static void
ssa_function_state(efunction_t *function, esymbol_t *symbol, eint32_t state)
{
    eentry_t	*entry;

    entry = eget_hash(function->functions, symbol);
    if (entry == null) {
	enew(&entry, entry);
	entry->name = symbol;
	eput_hash(function->functions, entry);
    }
    else
	state |= eget_fixnum(entry->value);
    entry->value = efixnum(state);
}

static void
ssa_source(efunction_t *function)
{
    elabel_t		 *base;
    elabel_t		**dsts;
    east_list_t		 *list;
    east_node_t		 *node;
    elabel_t		**srcs;
    elabel_t		 *temp;
    eint32_t		  index;
    elabel_t		 *label;
    eint32_t		  offset;
    esymbol_t		**symbols;

    list = function->code;
    node = east_node(list);
    base = node->clabel.label;

    /* all symbols referenced in the function are "implicitly"
     * set in the first basic block */
    mpz_set_ui(base->set, 0);
    mpz_setbit(base->set, function->vsym->offset);
    mpz_sub_ui(base->set, base->set, 1);

    /* reset ssa counter */
    symbols = function->vsym->v.obj;
    if (function->name == null) {
	for (offset = 0; offset < function->vsym->offset; offset++)
	    symbols[offset]->ssa = 0;
    }
    else {
	/* ssa counter can also be used as GVN (global value numbering) */
	for (offset = 0; offset < function->vsym->offset; offset++)
	    if (!symbols[offset]->a_global)
		symbols[offset]->ssa = 0;
    }

    /* construct srcs vector */
    for (label = base; label; label = label->next) {
	dsts = label->dsts->v.obj;
	for (offset = 0; offset < label->dsts->offset; offset++) {
	    temp = dsts[offset];
	    srcs = temp->srcs->v.obj;
	    for (index = 0; index < temp->srcs->offset; index++) {
		if (srcs[index] == label)
		    break;
	    }
	    if (index == temp->srcs->offset) {
		if (temp->srcs->offset >= temp->srcs->length) {
		    erenew_vector(temp->srcs, index + 4);
		    srcs = temp->srcs->v.obj;
		}
		srcs[temp->srcs->offset++] = label;
	    }
	}
    }
}

/*
 * check if a value ssa counter must be incremented at the start of
 * a basic block; required if value is read before any assignment,
 * and value can come from 2 or more basic blocks (including self
 * in case of loops)
 */
#define zmsk				mpq_denref(thr_qr)
#define zuse				thr_z0
#define zset				thr_zr
static void
ssa_loops_increment(elabel_t *label, eint32_t ident)
{
    elabel_t		**dsts;
    eint32_t		  offset;

    if (label->ident <= ident && !mpz_tstbit(zmsk, label->ident)) {
	mpz_setbit(zmsk, label->ident);
	++label->loop;
	dsts = label->dsts->v.obj;
	for (offset = 0; offset < label->dsts->offset; offset++)
	    ssa_loops_increment(dsts[offset], ident);
    }
}

static void
ssa_loops_extend(elabel_t *label, eint32_t ident, eint32_t loop)
{
    elabel_t		**dsts;
    eint32_t		  offset;

    if (label->ident <= ident && !mpz_tstbit(zmsk, label->ident)) {
	mpz_setbit(zmsk, label->ident);
	label->loop = loop;
	dsts = label->dsts->v.obj;
	for (offset = 0; offset < label->dsts->offset; offset++)
	    ssa_loops_extend(dsts[offset], ident, loop);
    }
}

static void
ssa_loops(elabel_t *label)
{
    elabel_t		**dsts;
    elabel_t		 *temp;
    eint32_t		  offset;

    mpz_set_ui(zset, 0);
    for (; label; label = label->next) {
	dsts = label->dsts->v.obj;

	/* mark destinations that may be in loop */
	if (label->sref) {
	    for (offset = 0; offset < label->dsts->offset; offset++)
		ssa_srefs(dsts[offset]);
	}

	for (offset = 0; offset < label->dsts->offset; offset++) {
	    temp = dsts[offset];
	    if (temp->ident <= label->ident) {
		mpz_set_ui(zmsk, 0);
		if (mpz_tstbit(zset, temp->ident))
		    ssa_loops_extend(temp, label->ident, temp->loop);
		else {
		    mpz_setbit(zset, temp->ident);
		    ssa_loops_increment(temp, label->ident);
		}
	    }
	}
    }
}

static void
ssa_srefs(elabel_t *label)
{
    elabel_t		**dsts;
    eint32_t		  offset;

    if (label->sref)
	return;
    label->sref = 1;
    dsts = label->dsts->v.obj;
    for (offset = 0; offset < label->dsts->offset; offset++)
	ssa_srefs(dsts[offset]);
}

static void
ssa_recurse(elabel_t *label, elabel_t *source, ssa_recurse_t *recurse)
{
    unsigned long	  offset;
    elabel_t		**vector;

restart:
    /* Note: to avoid a function call, check if not set before calling */
    mpz_setbit(zmsk, source->ident);

    /* mask of values defined in this block */
    if (mpz_sgn(source->set)) {
	mpz_and(zuse, zset, source->set);

	/* if anything new in this basic block */
	if (mpz_sgn(zuse)) {
	    vector = ssa_vector->v.obj;
	    offset = mpz_scan1(zuse, 0);
	    /* dont need to check it in source basic blocks */
	    if (recurse && recurse->esp == null) {
		if (thread_self->esp - thread_self->ess >= thread_self->len)
		    evm_stack_resize(8192);
		recurse->esp = thread_self->esp++;
		if (thread_self->esp[-1].t != t_shz)
		    eget_mpz(zset, (thread_self->esp - thread_self->ess) - 1);
		else
		    mpz_set(thread_self->esp[-1].v.mpz, zset);
		recurse->mpz = thread_self->esp[-1].v.mpz;
	    }

	    do {
		/* first time value is set */
		if (vector[offset] == null)
		    vector[offset] = source;

		/* multiple paths set value */
		else {
		    assert(vector[offset] != source);
		    mpz_setbit(label->phi, offset);
		}
		mpz_clrbit(zset, offset);
		offset = mpz_scan1(zuse, offset + 1);
	    } while (offset != ULONG_MAX);
	}
    }

    /* if there is still something to verify */
    if (mpz_sgn(zset) && source->srcs->offset) {
	vector = source->srcs->v.obj;
	if (source->srcs->offset > 1) {
	    ssa_recurse_t	stack;

	    stack.esp = null;
	    for (offset = 0; offset < source->srcs->offset - 1; offset++) {
		if (!mpz_tstbit(zmsk, vector[offset]->ident)) {
		    ssa_recurse(label, vector[offset], &stack);
		    if (stack.esp)
			mpz_xor(zset, stack.mpz, label->phi);
		}
	    }
	    if (stack.esp)
		thread_self->esp = stack.esp;
	    source = vector[offset];
	}
	else
	    source = vector[0];
	if (!mpz_tstbit(zmsk, source->ident)) {
	    recurse = null;
	    goto restart;
	}
    }
}

static void
ssa_phi_recurse(elabel_t *label, eint32_t loop)
{
    elabel_t		**dsts;
    eint32_t		  offset;

    if (/* if already visited */
	mpz_tstbit(zmsk, label->ident) ||
	/* loop groups are nested */
	label->loop < loop)
	return;
    mpz_setbit(zmsk, label->ident);
    if (label->loop == loop) {
	mpz_ior(zset, zset, label->set);
	mpz_ior(zuse, zuse, label->use);
    }
    dsts = label->dsts->v.obj;
    for (offset = 0; offset < label->dsts->offset; offset++)
	ssa_phi_recurse(dsts[offset], loop);
}

static void
ssa_linear(elabel_t *label, elabel_t **labels)
{
    unsigned long	  bit;
    eint32_t		  ident;
    elabel_t		**vector;

    vector = ssa_vector->v.obj;
    for (bit = mpz_scan1(zset, 0);
	 bit != ULONG_MAX; bit = mpz_scan1(zset, bit)) {
	/* offset to start scanning for a set in linear representation */
	if (vector[bit] && vector[bit]->ident < label->ident)
	    ident = vector[bit]->ident + 1;
	else
	    ident = 0;
	for (; ident < label->ident; ident++) {
	    if (/* if there is a set in another code path */
		mpz_tstbit(labels[ident]->set, bit) ||
		/* if there is an implicit increment in another code path */
		mpz_tstbit(labels[ident]->phi, bit)) {
		mpz_setbit(label->phi, bit);
		break;
	    }
	}
	++bit;
    }
}

static void
ssa_labels(efunction_t *function)
{
    east_list_t		 *list;
    eint32_t		  loop;
    east_node_t		 *node;
    elabel_t		**srcs;
    empz_t		  zall;
    elabel_t		 *label;
    unsigned long	  offset;
    elabel_t		**vector;

    list = function->code;
    node = east_node(list);
    if (ssa_vector->length <= function->vsym->offset)
	erenew_vector(ssa_vector, (function->vsym->offset + 16) & ~15);
    vector = ssa_vector->v.obj;
    label = node->clabel.label;

    /* check labels in loop */
    ssa_loops(label);

    /* if first label is non looping */
    if (label->srcs->offset == 0)
	label = label->next;

    for (loop = 0; label; label = label->next) {
	if (loop != label->loop) {
	    if (loop < label->loop) {
		mpz_set_ui(zmsk, 0);
		mpz_set_ui(zset, 0);
		mpz_set_ui(zuse, 0);
		ssa_phi_recurse(label, label->loop);
		/* mask of values with multiple sources within the loop */
		mpz_and(label->phi, zset, zuse);
		zall = zset;
		mpz_ior(zall, label->use, label->phi);
	    }
	    else
		zall = label->use;
	    loop = label->loop;
	}
	else
	    zall = label->use;

	/* if no value used in unknown state */
	if (mpz_sgn(label->use) == 0)
	    continue;

	/* initialize to undefined source */
	for (offset = mpz_scan1(zall, 0);
	     offset != ULONG_MAX; offset = mpz_scan1(zall, offset))
	    vector[offset++] = null;

	/* visit a code path only once */
	mpz_set_ui(zmsk, 0);

	/* check for multiple paths setting values */
	srcs = label->srcs->v.obj;
	for (offset = 0; offset < label->srcs->offset; offset++) {

	    /* mask out what is already known to require a counter increment */
	    mpz_com(zuse, label->phi);
	    mpz_and(zset, zuse, label->use);

	    /* if all values resolved */
	    if (mpz_sgn(zset) == 0)
		break;

	    /* collect information for this source */
	    ssa_recurse(label, srcs[offset], null);
	}

	/* check for implicit ssa counter upgrades in linear code that may
	 * cause a load in a branch not have a counter upgrade after a
	 * store in another branch; this may happen in if-else constructs */
	mpz_com(zuse, label->phi);
	mpz_and(zset, zuse, label->use);
	if (mpz_sgn(zset))
	    ssa_linear(label, function->labels->v.obj);
    }
}
#undef zset
#undef zuse
#undef zmsk

/*
 * set ssa number/generation value in opcode list
 */
static void
ssa_name(efunction_t *function)
{
    eint32_t		  i;
    eint32_t		  bp;
    eint32_t		  sp;
    eint32_t		  op;
    unsigned long	  bit;
    ehash_t		 *hash;
    east_list_t		 *list;
    east_node_t		 *node;
    east_node_t		 *temp;
    eentry_t		 *entry;
    elabel_t		 *label;
    eint32_t		  stack;
    esymbol_t		 *value;
    efunction_t		 *lambda;
    elabel_t		 *target;
    eint32_t		  stklen;
    esymbol_t		**changes;
    esymbol_t		**escapes;
    esymbol_t		**symbols;

    /* count number of entries required in ssa invalidate vectors */
    ssa_changes->offset = ssa_escapes->offset = 0;
    symbols = function->vsym->v.obj;
    changes = ssa_changes->v.obj;
    escapes = ssa_escapes->v.obj;

    for (i = 0; i < function->vsym->offset; i++) {
	value = symbols[i];

	/* if an aggregate type object */
	if (mpz_tstbit(function->esc, i)) {
	    if (ssa_changes->offset >= ssa_changes->length) {
		erenew_vector(ssa_changes, (ssa_changes->offset + 16) & ~15);
		changes = ssa_changes->v.obj;
	    }
	    changes[ssa_changes->offset++] = value;
	    if (ssa_escapes->offset >= ssa_escapes->length) {
		erenew_vector(ssa_escapes, (ssa_escapes->offset + 16) & ~15);
		escapes = ssa_escapes->v.obj;
	    }
	    escapes[ssa_escapes->offset++] = value;
	}
	/* if address of variable was taken */
	else if (mpz_tstbit(function->ptr, i) ||
		/* if a global symbol or possible argument by reference
		 * or that may have a global pointer elsewhere */
		 value->a_field || value->a_global ||
		 /* special, "cosmetic", check for 'this' */
		 (value->name != symbol_this->name && value->offset < 0)) {
	    if (ssa_changes->offset >= ssa_changes->length) {
		erenew_vector(ssa_changes, (ssa_changes->offset + 16) & ~15);
		changes = ssa_changes->v.obj;
	    }
	    changes[ssa_changes->offset++] = value;
	}
    }

    ssa_clear_loads(function);
    ssa_clear_stores(function);

#define check_stack(offset)						\
    do {								\
	if (stack < offset)						\
	    stack = offset;						\
	else {								\
	    assert(offset >= 2);					\
	}								\
    } while (0)

    /* extra 2 is to "simulate" saved ip and bp, and calculate proper
     * value for the enter opcode at the start of a function */
    stack = function->args + function->hash->offset + 2;
    check_stack(stack);
    bp = function->args + 1;
    op = sp = bp + 2;
    label = null;
    for (list = function->code; list; list = east_next(list)) {
	node = east_node(list);
	switch (node->code) {
	    case eop_label:			case eop_block:
		label = node->clabel.label;
#if 0
		if (!label->ijmp) {
		    stklen = sp - op;
		    if (label->stack == -1)
			label->stack = stklen;
		    else
			assert(label->stack == stklen);
		}
#endif
		/* check for values that can come from multiple basic blocks */
		for (bit = mpz_scan1(label->phi, 0);
		     bit != ULONG_MAX; bit = mpz_scan1(label->phi, bit))
		    ssa_count_inc(symbols[bit++]);
		/* if there is a pointer store */
		if (label->sref)
		    ssa_invalidate(changes, ssa_changes->offset);
		break;
		/* prepare to call function */
	    case eop_begin:
		i = node->cstack.length;
		check_stack(sp + i);
		sp += i;
		break;
		/* push value to stack */
	    case eop_off:			case eop_push:
	    case eop_pusht:			case eop_pushv:
		++sp;
		check_stack(sp);
		break;
	    case eop_catch:
		check_stack(sp);
		break;
		/* adjust stack */
	    case eop_unwind:
		/* if the last/normal unwind of try */
		if (node->cexcept.arg) {
		    sp -= 4;
		    check_stack(sp);
		}
		/* FALLTHROUGH */
		/* invalidate value */
	    case eop_nil:			case eop_int:
	    case eop_float:			case eop_ll:
	    case eop_pd:			case eop_pdt:
	    case eop_pb:			case eop_pbt:
	    case eop_lvi:			case eop_lr:
	    case eop_pvi:			case eop_pr:
	    case eop_lref:			case eop_pp:
	    case eop_anon:			case eop_enter:
	    case eop_fref:			case eop_inc:
	    case eop_dec:			case eop_bool:
	    case eop_not:			case eop_com:
	    case eop_inv:			case eop_neg:
	    case eop_signbit:			case eop_signum:
	    case eop_rational:			case eop_integer_p:
	    case eop_rational_p:		case eop_float_p:
	    case eop_real_p:			case eop_complex_p:
	    case eop_number_p:			case eop_finite_p:
	    case eop_inf_p:			case eop_nan_p:
	    case eop_num:			case eop_den:
	    case eop_real:			case eop_imag:
	    case eop_arg:			case eop_conj:
	    case eop_floor:			case eop_trunc:
	    case eop_round:			case eop_ceil:
	    case eop_abs:			case eop_sqrt:
	    case eop_cbrt:			case eop_sin:
	    case eop_cos:			case eop_tan:
	    case eop_asin:			case eop_acos:
	    case eop_atan:			case eop_sinh:
	    case eop_cosh:			case eop_tanh:
	    case eop_asinh:			case eop_acosh:
	    case eop_atanh:			case eop_proj:
	    case eop_exp:			case eop_log:
	    case eop_log2:			case eop_log10:
	    case eop_szof:			case eop_szofva:
	    case eop_szofdimi:			case eop_tpof:
	    case eop_rankof:			case eop_lva:
	    case eop_lvai:			case eop_throw:
	    case eop_pva:			case eop_new:
	    case eop_vnewi:			case eop_renewi:
	    case eop_ret:			case eop_rett:
	    case eop_reti:			case eop_retit:
	    case eop_exit:			case eop_intp_0:
	    case eop_intp_v:			case eop_intp_n1:
	    case eop_realp_v:			case eop_nump_0:
	    case eop_nump_v:			case eop_setup:
		break;
	    case eop_vnew:
		sp -= node->cvector.rank - 1;
		check_stack(sp);
		break;
	    case eop_jh:
		stklen = sp - op;
		hash = els[immediate_offset(node)];
		for (i = 0; i < hash->size; i++) {
		    for (entry = hash->entries[i]; entry; entry = entry->next) {
			temp = entry->value;
			target = temp->clabel.label;
			if (target->stack == -1)
			    target->stack = stklen;
			else
			    assert(target->stack == stklen);
		    }
		}
		break;
	    case eop_j:				case eop_jt:
	    case eop_jf:
		/* check operands in stack for block */
		target = node->clabel.label;
		stklen = sp - op;
		if (target->stack == -1)
		    target->stack = stklen;
		else
		    assert(target->stack == stklen);
		break;
		/* pop stack and invalidate value */
	    case eop_lh:			case eop_ne:
	    case eop_lt:			case eop_le:
	    case eop_eq:			case eop_ge:
	    case eop_gt:			case eop_and:
	    case eop_or:			case eop_xor:
	    case eop_mul2:			case eop_div2:
	    case eop_shl:			case eop_shr:
	    case eop_add:			case eop_sub:
	    case eop_mul:			case eop_div:
	    case eop_trunc2:			case eop_rem:
	    case eop_atan2:			case eop_pow:
	    case eop_hypot:			case eop_complex:
	    case eop_szofdim:			case eop_subtpof:
		--sp;
		check_stack(sp);
		break;
	    case eop_lv:			case eop_pv:
		sp -= node->cvector.rank;
		check_stack(sp);
		break;
		/* pop stack without invalidate */
	    case eop_sva:
		--sp;
		check_stack(sp);
		break;
		/* pop value from stack */
	    case eop_pop:
		--sp;
		check_stack(sp);
		break;
		/* pop stack with invalidate */
	    case eop_sref:
		--sp;
		check_stack(sp);
		ssa_invalidate(changes, ssa_changes->offset);
		break;
		/* pop stack with non scalar invalidate */
	    case eop_svi:			case eop_sr:
		--sp;
		check_stack(sp);
		ssa_invalidate(escapes, ssa_escapes->offset);
		break;
		/* pop value from stack, with non scalar invalidate */
	    case eop_mv:			case eop_vmv:
	    case eop_renew:
		--sp;
		check_stack(sp);
		ssa_invalidate(escapes, ssa_escapes->offset);
		break;
		/* pop value from stack, with non scalar invalidate */
	    case eop_sh:
		sp -= 2;
		check_stack(sp);
		ssa_invalidate(escapes, ssa_escapes->offset);
		break;
	    case eop_sv:
		sp -= node->cvector.rank + 1;
		check_stack(sp);
		ssa_invalidate(escapes, ssa_escapes->offset);
		break;
		/* load value from stack */
	    case eop_ls:
		i = node->cstack.offset;
		check_stack(sp + i);
		break;
		/* store value to stack */
	    case eop_ss:			case eop_sst:
	    case eop_ssv:
		i = node->cstack.offset;
		check_stack(sp + i);
		break;
		/* construct shared load opcode */
	    case eop_ld:			case eop_ldt:
	    case eop_lb:			case eop_lbt:
	    case eop_lc:
		value = node->csymbol.symbol;
		essa_load_symbol(function, label, list);
		break;
		/* invalidate load of value */
	    case eop_sd:			case eop_sdt:
	    case eop_sb:			case eop_sbt:
	    case eop_sc:
		essa_store_symbol(function, label, list);
		break;
		/* function calls, with invalidate */
	    case eop_call:			case eop_ecm:
		sp -= node->capply.length;
		check_stack(sp);
		/* if first pass, or the called function modifies
		 * global arguments or global state */
		lambda = node->capply.symbol->value;
		if (!cfg_pass || lambda->flags)
		    ssa_invalidate(changes, ssa_changes->offset);
		break;
	    case eop_meth:
		sp -= node->capply.length;
		check_stack(sp);
		/* virtual methods not considered "constants" */
		ssa_invalidate(changes, ssa_changes->offset);
		break;
	    case eop_blt:
		sp -= node->capply.length;
		check_stack(sp);
		/* builtins can only modify pointers or aggregates */
		if (!cfg_pass || (function->flags & fun_esc))
		    ssa_invalidate(changes, ssa_changes->offset);
		break;
	    case eop_apply:
		sp -= node->capply.length;
		check_stack(sp);
		/* consider anything may change after calling a
		 * function through a pointer */
		ssa_invalidate(changes, ssa_changes->offset);
		break;
		/* stack adjust after apply */
	    case eop_val:
		sp -= node->cstack.length;
		check_stack(sp);
		break;
		/* adjust stack */
	    case eop_try:
		sp += 4;
		check_stack(sp);
		/* FALLTHROUGH */
	    case eop_aret:			case eop_vas:
	    case eop_svai:			case eop_note:
		break;
	    case eop_lhref:
		sp += node->chash.arg != 0;
		check_stack(sp);
		break;
	    case eop_lvref:
		sp -= node->cvector.rank - !!node->cvector.arg - 1;
		check_stack(sp);
		break;
	    case eop_lrref:
		sp += !!node->crecord.arg + 1;
		break;
	    case eop_shref:
		sp -= !!node->chash.arg + 1;
		check_stack(sp);
		break;
	    case eop_svref:
		sp -= !!node->cvector.arg + 1;
		check_stack(sp);
		break;
	    case eop_srref:
		sp -= !!node->crecord.arg + 1;
		check_stack(sp);
		break;
	    default:
#if DEBUG
		eparse_error(null, "unexpected opcode %d", node->code);
#endif
		break;
	}
    }
#undef check_stack

    /* adjust initial stack of basic blocks */
    list = function->code;
    node = east_node(list);
    label = node->clabel.label;
    for (; label; label = label->next) {
	/* if block is not the target of a "complex" expression */
	if (label->stack < 0)
	    label->stack = 0;
    }

    function->stack = stack - 2;
}

static essa_load_t *
ssa_load_allocate(efunction_t *function, eint32_t bit, eint32_t ssa)
{
    essa_load_t		 *load;
    essa_load_t		**loads;
    eint32_t		  length;

    if (bit >= function->loads->length)
	erenew_vector(function->loads, (bit + 8) & ~7);
    loads = function->loads->v.obj;
    length = (ssa + 4) & ~3;
    if ((load = loads[bit]) == null) {
	enew_object((eobject_t *)(loads + bit), t_ssa_load,
		    sizeof(essa_load_t));
	load = loads[bit];
	enew_object((eobject_t *)&load->data, t_resize | t_void,
		    length * sizeof(essa_load_data_t));
	load->top = -1;
    }
    else if (length >=
	     eobject_length(load->data) / sizeof(essa_load_data_t))
	eresize_object((eobject_t *)&load->data,
		       length * sizeof(essa_load_data_t));

    return (load);
}

void
essa_load_symbol(efunction_t *function, elabel_t *label, east_list_t *list)
{
    eint32_t		 bit;
    eint32_t		 ssa;
    esymbol_t		*atom;
    essa_load_data_t	*data;
    east_node_t		*node;
    essa_load_t		*load;
    eentry_t		*entry;
    eint32_t		 length;

    node = east_node(list);
    atom = node->csymbol.symbol;
    ssa = ssa_count_get(atom);
    entry = eget_hash(function->hsym, atom);
    bit = eget_fixnum(entry->value);

    load = ssa_load_allocate(function, bit, ssa);
    data = load->data + ssa;
    if (data->lists == null) {
	length = 8 * sizeof(eobject_t);
	enew_object((eobject_t *)&data->lists,  t_resize | t_void, length);
	enew_object((eobject_t *)&data->labels, t_resize | t_void, length);
    }
    else if ((data->offset & 7) == 0) {
	length = (data->offset + 8) * sizeof(eobject_t);
	eresize_object((eobject_t *)&data->lists,  length);
	eresize_object((eobject_t *)&data->labels, length);
    }

    if (data->node == null) {
	assert(data->offset == 0);
	/* first entry, create a new object to avoid the
	 * risk of on multiple passes, some optimization
	 * end up modifying/using the wrong shared object */
	switch (node->code) {
	    case eop_ld:			case eop_ldt:
	    case eop_lb:			case eop_lbt:
		node = ecode_symbol_load(atom);
		break;
	    case eop_lc:
		node = ecode_class_load(atom);
		break;
	    default:
		abort();
	}
	data->node = node;
	node->csymbol.bit = bit;
	node->csymbol.ssa = ssa;

	/* first load recorded? */
	if (load->top < 0)
	    load->bot = load->top = ssa;
	else
	    load->top = ssa;
    }
    data->lists [data->offset] = list;
    data->labels[data->offset] = label;
    ++data->offset;

    /* shared entry */
    east_node(list) = data->node;
}

void
essa_del_list_use(efunction_t *function, east_list_t *list)
{
    essa_load_data_t	 *data;
    east_node_t		 *node;
    essa_load_t		 *load;
    essa_load_t		**loads;
    eint32_t		  offset;

    loads = function->loads->v.obj;
    node = east_node(list);
    load = loads[node->csymbol.bit];
    data = load->data + node->csymbol.ssa;
    assert(data->node == node);

    for (offset = 0; offset < data->offset; offset++) {
	if (data->lists[offset] == list) {
	    if (--data->offset) {
		data->lists [offset] = data->lists [data->offset];
		data->labels[offset] = data->labels[data->offset];
		data->lists [data->offset] = null;
		data->labels[data->offset] = null;
	    }
	    else
		essa_del_node_use(load, node);
	    return;
	}
    }

    /* not reached */
    abort();
}

void
essa_del_node_use(essa_load_t *load, east_node_t *node)
{
    essa_load_data_t	*data;
    eint32_t		 length;

    data = load->data + node->csymbol.ssa;
    assert(data->node == node);

    /* last load? */
    if (load->bot == load->top) {
	load->bot = 0;
	load->top = -1;
    }
    /* update first load */
    else if (node->csymbol.ssa == load->bot) {
	for (; load->bot < load->top; load->bot++)
	    if (load->data[load->bot].node)
		break;
    }
    /* update last load */
    else if (node->csymbol.ssa == load->top) {
	for (; load->top > load->bot; load->top--)
	    if (load->data[load->top].node)
		break;
    }
    data->node = null;
    if (data->offset) {
	length = data->offset * sizeof(eobject_t);
	memset(data->lists,  0, length);
	memset(data->labels, 0, length);
	data->offset = 0;
    }
}

static void
ssa_clear_loads(efunction_t *function)
{
    essa_load_data_t	 *data;
    essa_load_t		 *load;
    essa_load_t		**loads;
    eint32_t		  index;
    eint32_t		  length;
    eint32_t		  offset;

    loads = function->loads->v.obj;
    for (offset = 0; offset < function->loads->length; offset++) {
	if ((load = loads[offset]) == null)
	    continue;
	for (index = load->bot; index <= load->top; index++) {
	    data = load->data + index;
	    if (data->offset) {
		data->node = null;
		length = data->offset * sizeof(eobject_t);
		memset(data->lists,  0, length);
		memset(data->labels, 0, length);
		data->offset = 0;
	    }
	}
	load->bot = 0;
	load->top = -1;
    }
}

void
essa_store_symbol(efunction_t *function, elabel_t *label, east_list_t *list)
{
    esymbol_t		*atom;
    eobject_t		*data;
    east_node_t		*node;
    eentry_t		*entry;
    essa_store_t	*store;
    eint32_t		 length;

    node = east_node(list);
    atom = node->csymbol.symbol;
    node->csymbol.ssa = ssa_count_inc_get(atom);
    entry = eget_hash(function->hsym, atom);
    node->csymbol.bit = eget_fixnum(entry->value);

    if (node->csymbol.bit >= function->stores->length)
	erenew_vector(function->stores, (node->csymbol.bit + 8) & ~7);
    data = function->stores->v.obj;

    length = (node->csymbol.ssa + 4) & ~3;
    if ((store = data[node->csymbol.bit]) == null) {
	enew_object(data + node->csymbol.bit, t_ssa_store,
		    sizeof(essa_store_t));
	store = data[node->csymbol.bit];
	enew_object((eobject_t *)&store->data, t_resize | t_void,
		    length * sizeof(essa_store_data_t));
    }
    else if (length >=
	     eobject_length(store->data) / sizeof(essa_store_data_t))
	eresize_object((eobject_t *)&store->data,
		       length * sizeof(essa_store_data_t));

    store->data[node->csymbol.ssa].list  = list;
    store->data[node->csymbol.ssa].label = label;

    /* first store recorded? */
    if (store->top == 0)
	store->bot = store->top = node->csymbol.ssa;
    else
	store->top = node->csymbol.ssa;

    /* ensure check for symbol load dereferences bound memory,
     * without the need of "complicated" extra tests, in case
     * there are no loads after a store */
    (void)ssa_load_allocate(function, node->csymbol.bit, node->csymbol.ssa);
}

void
essa_del_list_set(efunction_t *function, east_list_t *list)
{
    eobject_t		*data;
    east_node_t		*node;
    essa_store_t	*store;

    node = east_node(list);
    data = function->stores->v.obj;
    store = data[node->csymbol.bit];
    assert(store->data[node->csymbol.ssa].list == list);
    store->data[node->csymbol.ssa].list  = null;
    store->data[node->csymbol.ssa].label = null;

    /* last store? */
    if (store->bot == store->top)
	store->bot = store->top = 0;

    /* update first store */
    else if (node->csymbol.ssa == store->bot) {
	for (; store->bot < store->top; store->bot++)
	    if (store->data[store->bot].list)
		break;
    }

    /* update last store */
    else if (node->csymbol.ssa == store->top) {
	for (; store->top > store->bot; store->top--)
	    if (store->data[store->top].list)
		break;
    }
}

static void
ssa_clear_stores(efunction_t *function)
{
    eobject_t		*data;
    essa_store_t	*store;
    eint32_t		 offset;

    data = function->stores->v.obj;
    for (offset = 0; offset < function->stores->length; offset++) {
	if ((store = data[offset]) == null || store->bot == 0)
	    continue;
	store->bot = store->top = 0;
	memset(store->data, 0, eobject_length(store->data));
    }
}
